﻿using MQTTnet.Protocol;

using MQTTNet.Client.Enums;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;


namespace MQTTNet.Client.Common
{
    public static class Common
    {
        private static string _json_regex = $@"^\s*(?:\{{(?:[^{{}}]*|(?R))*\}}|\[(?:[^\[\]]*|(?R))*\])\s*$";
        private const string assmblyName = "System.Reflection.PropertyInfo";
        private const string assmblyBuilder = "System.Reflection.Emit.PropertyBuilder";

        private static JsonSerializerOptions option = new JsonSerializerOptions()
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };
        public static string ReplaceAll(this string str)
        {
            string[] strs = str.Split('/');
            var result = strs.Select(s => s.Length > 1 ?
            s.Replace(@"#", @"\#")
            .Replace(@"+", @"\+") :
            s.Replace(@"#", @"[^/]+")
            .Replace(@"+", @"[^/]+")).ToArray();
            return $@"^{string.Join("/", result)}$";

        }
        public static bool IsInherit(this Type type,Type remoteType)
        {
            Type baseType = type.BaseType;
            while (baseType!=null)
            {
                if (baseType == remoteType) return true;
                baseType = baseType.BaseType;
            }
            return false;
        }
        public static bool IsJson(this string? _str_json)
        {

            try
            {
                if (_str_json == null) return false;
                JsonSerializer.Deserialize(_str_json, typeof(object), option);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }

        }
        public static bool TopicMatch(this string pattern, string topic)
        {
            if (topic.Length == 1 && (topic == "+" || topic == "#")) return true;
            Regex regex = new Regex(pattern.ReplaceAll());
            return regex.IsMatch(topic);
        }
        public static string ClearSpace(this string _str)
           => _str.Replace("\n", string.Empty)
           .Replace("\0", string.Empty)
           .Replace("\r", string.Empty)
           .Replace("\t", string.Empty)
           .Replace(" ", string.Empty);

        public static string ObjectToJson(this object? _obj)
        {
            try
            {
                if (_obj == null) return string.Empty;
                return JsonSerializer.Serialize(_obj, option);
            }
            catch (Exception ex)
            {
                return string.Empty;
            }
        }
        public static object? CopyObjectByType(this object? _obj, Type _type)
        {
            _obj = _obj.JsonElementToObject();
            var sourceType = _obj?.GetType();
            PropertyInfo[]? sourcePropperties = sourceType?.GetProperties();
            var dec_obj = Activator.CreateInstance(_type);
            Type _objType = dec_obj.GetType();
            var desery = _objType.GetProperties();
            foreach (var item in desery)
            {
                try
                {
                    var _source = sourcePropperties?.Where(s => s.Name == item.Name).FirstOrDefault();
                    if (_source != null|| sourceType==typeof(Dictionary<string,object>))
                    {
                        object? value = _source != null ? _source.GetValue(_obj)
                            : sourceType == typeof(Dictionary<string, object>) ?
                            (_obj as Dictionary<string, object>).Where(s => s.Key.ToLower() == item.Name.ToLower()).FirstOrDefault().Value:
                            null;
                        if (value == null)
                        {
                            item.SetValue(dec_obj, item.DefaultValue());
                            continue;
                        }
                        Type tmp_type = value.GetType();
                        if (tmp_type.IsCustomClass())
                        {
                            if (item.PropertyType.IsArray)
                            {

                                Type elementType = item.PropertyType.GetElementType();
                                var _dec_obj = (IList)Activator.CreateInstance(item.PropertyType);
                                Type _obj_type = _dec_obj.GetType();
                                var method = _obj_type.GetMethod("Add");
                                (value as List<object>)?.ForEach(s => {
                                    var s_type = s.GetType();
                                    if (s_type == typeof(JsonElement))
                                        method?.Invoke(_dec_obj, new object[] { JsonElementToObject(s) });
                                    else if (s_type == typeof(Dictionary<string, object>) || s_type == typeof(Dictionary<string, object?>))
                                        method?.Invoke(_dec_obj, new object[] { CopyObjectByType(s, elementType) });
                                    else
                                        method?.Invoke(_dec_obj, new object[] { Convert.ChangeType(s, elementType) });
                                });
                                Array array = Array.CreateInstance(elementType, _dec_obj.Count);
                                _dec_obj.CopyTo(array, 0);
                                item.SetValue(dec_obj, array);
                            }
                            else if (item.PropertyType.IsGenericType && item.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
                            {
                                Type elementType = item.PropertyType.GetGenericArguments().FirstOrDefault();
                                if (elementType != null)
                                {
                                    var _dec_obj = (IList)Activator.CreateInstance(item.PropertyType);
                                    Type _obj_type = _dec_obj.GetType();
                                    var method = _obj_type.GetMethod("Add");
                                    (value as List<object>)?.ForEach(s => {
                                        var s_type = s.GetType();
                                        if(s_type == typeof(JsonElement))
                                            method?.Invoke(_dec_obj, new object[] { JsonElementToObject(s) });
                                        else if(s_type == typeof(Dictionary<string,object>)|| s_type == typeof(Dictionary<string, object?>))
                                            method?.Invoke(_dec_obj, new object[] { CopyObjectByType(s, elementType) });
                                        else
                                            method?.Invoke(_dec_obj, new object[] { Convert.ChangeType(s, elementType) });
                                    });
                                    item.SetValue(dec_obj, _dec_obj);
                                }
                                else
                                    item.SetValue(dec_obj, item.DefaultValue());
                            }
                            else
                                item.SetValue(dec_obj, value.CopyObjectByType(tmp_type));
                        }
                        else if (tmp_type == typeof(string))
                            item.SetValue(dec_obj, value.ToString());
                        else if (tmp_type.IsEnum)
                            item.SetValue(dec_obj, Enum.TryParse(item.PropertyType, (value ?? string.Empty).ToString(), out object result) ? result : 0);
                        else if (tmp_type == typeof(Guid))
                            item.SetValue(dec_obj, Guid.TryParse(value.ToString(), out Guid result) ? result : Guid.Empty);
                        else
                            item.SetValue(dec_obj, Convert.ChangeType(value, item.PropertyType));
                    }
                    else
                        item.SetValue(dec_obj, item.DefaultValue());
                }
                catch (Exception ex)
                {
                    item.SetValue(dec_obj, item.DefaultValue());
                }
            }
            return dec_obj;
        }

        public static object? JsonElementToObject(this object? obj)
        {
            if (obj == null) return null;
            Type type = obj.GetType();
            if (type == typeof(string))
            {
                obj = obj.ToString().JsonToObject<object>();
                type = obj.GetType();
            }
            if (type != typeof(JsonElement)) return obj;
            JsonElement element = (JsonElement)obj;
            object _obj = new object();
            JsonElement.ObjectEnumerator enumerateObject;
            List<PropertyInfo> sourcePropperties = new List<PropertyInfo>();
            switch (element.ValueKind)
            {
                case JsonValueKind.Object:
                    enumerateObject = element.EnumerateObject();
                    Dictionary<string,object> pairs = new Dictionary<string, object>();
                    foreach (JsonProperty item in enumerateObject)
                    {
                        var tmp = JsonElementToObject(item.Value);
                        pairs.Add(item.Name, tmp);
                    }
                    _obj = pairs;
                    break;
                case JsonValueKind.Array:
                    var enumerateArray = element.EnumerateArray();
                    List<object> array = new List<object>();
                    foreach (var item in enumerateArray)
                    {
                        array.Add(JsonElementToObject(item));
                    }
                    _obj = array;
                    break;
                case JsonValueKind.String:
                    if (element.TryGetDateTime(out DateTime str_value))
                        _obj = str_value;
                    else if (element.TryGetDateTimeOffset(out DateTimeOffset str_value1))
                        _obj = str_value1;
                    else if (element.TryGetGuid(out Guid str_value2))
                        _obj = str_value2;
                    else
                        _obj = element.GetString();
                    break;
                case JsonValueKind.Number:
                    if (element.TryGetByte(out byte value))
                        _obj = value;
                    else if (element.TryGetDecimal(out decimal value1))
                        _obj = value1;
                    else if (element.TryGetDouble(out double value2))
                        _obj = value2;
                    else if (element.TryGetInt16(out short value3))
                        _obj = value3;
                    else if (element.TryGetInt32(out int value4))
                        _obj = value4;
                    else if (element.TryGetInt64(out long value5))
                        _obj = value5;
                    else if (element.TryGetSByte(out sbyte value6))
                        _obj = value6;
                    else if (element.TryGetSingle(out float value7))
                        _obj = value7;
                    else if (element.TryGetUInt16(out ushort value8))
                        _obj = value8;
                    else if (element.TryGetUInt32(out uint value9))
                        _obj = value9;
                    else if (element.TryGetUInt64(out ulong value10))
                        _obj = value10;
                    else
                        _obj = 0;
                    break;
                case JsonValueKind.True:
                    _obj = true;
                    break;
                case JsonValueKind.False:
                    _obj = false;
                    break;
                case JsonValueKind.Null:
                    _obj = null;
                    break;
                default:
                    _obj = null;
                    break;
            }
            return _obj;
        }
        private static object? DefaultValue(this PropertyInfo obj)
        {
            try
            {
                object? type = null;
                if (obj.PropertyType == typeof(string))
                    type = string.Empty;
                else if (obj.PropertyType.IsValueType)
                {
                    if (obj.PropertyType.IsEnum)
                        type = Enum.ToObject(obj.PropertyType, 0);
                    type = Activator.CreateInstance(obj.PropertyType);
                }
                return type;
            }
            catch (Exception ex)
            {
                return null;
            }

        }
        private static object? DefaultValue(this Type obj)
        {
            try
            {
                object? type = null;
                if (obj == typeof(string))
                    type = string.Empty;
                else if (obj.IsValueType)
                {
                    if (obj.IsEnum)
                        type = Enum.ToObject(obj, 0);
                    type = Activator.CreateInstance(obj);
                }
                return type;
            }
            catch (Exception ex)
            {
                return null;
            }

        }
        public static object? JsonToObject(this string _str, Type _type)
        {
            try
            {
                object? obj = Activator.CreateInstance(_type);
                if (string.IsNullOrEmpty(_str))
                    return obj;
                obj = JsonSerializer.Deserialize(_str, _type, option);
                return obj;
            }
            catch (Exception ex)
            {
                return null;
            }
        }
        public static T JsonToObject<T>(this string _str)
        {
            try
            {
                if (string.IsNullOrEmpty(_str))
                    return default(T);
                return JsonSerializer.Deserialize<T>(_str, option) ?? default(T);
            }
            catch (Exception ex)
            {
                return default(T);
            }
        }
        internal static bool IsCustomClass(this Type type)
        {
            if (type == typeof(string)) return false;
            return type.IsClass && !type.IsPrimitive && !type.IsValueType && !type.IsEnum;
        }

        public static Type GetParamInfoType(this ParameterInfo parameter)
            => parameter.ParameterType;
        public static object? StrToObjectParam(this string? str, ParameterInfo parameter)
        {
            try
            {
                if (parameter.ParameterType.IsCustomClass())
                {
                    return string.IsNullOrEmpty(str) ? null : str.JsonToObject(parameter.ParameterType);
                }
                if (parameter.ParameterType.IsEnum)
                    return Enum.TryParse(parameter.ParameterType, str, out object? result) ? result : null;
                if (parameter.ParameterType == typeof(string))
                    return string.IsNullOrEmpty(str) ? string.Empty : str;
                if (parameter.ParameterType == typeof(Guid))
                    return string.IsNullOrEmpty(str) ? Guid.Empty : Guid.TryParse(str, out Guid result) ? result : Guid.Empty;
                if (!string.IsNullOrEmpty(str))
                    return Convert.ChangeType(str, parameter.ParameterType);
                return Activator.CreateInstance(parameter.ParameterType);

            }
            catch (Exception ex)
            {
                return null;
            }
        }
        public static Type GetParamInfoType(this object obj) => obj.GetType();
        public static object? ObjectToObjectParam(this object? obj, ParameterInfo parameter)
        {
            try
            {
                obj = obj.JsonElementToObject();
                Type dataType = obj.GetType();
                var decType = parameter.ParameterType;
                if (decType.IsCustomClass())
                {
                    if (decType.IsArray)
                    {

                        Type elementType = dataType.GetElementType();
                        var _dec_obj = (IList)Activator.CreateInstance(dataType);
                        Type _obj_type = _dec_obj.GetType();
                        var method = _obj_type.GetMethod("Add");
                        (obj as List<object>)?.ForEach(s => {
                            var s_type = s.GetType();
                            if (s_type == typeof(JsonElement))
                                method?.Invoke(_dec_obj, new object[] { JsonElementToObject(s) });
                            else if (s_type == typeof(Dictionary<string, object>) || s_type == typeof(Dictionary<string, object?>))
                                method?.Invoke(_dec_obj, new object[] { CopyObjectByType(s, elementType) });
                            else
                                method?.Invoke(_dec_obj, new object[] { Convert.ChangeType(s, elementType) });
                        });
                        Array array = Array.CreateInstance(elementType, _dec_obj.Count);
                        _dec_obj.CopyTo(array, 0);
                        return array;
                    }
                    else if (decType.IsGenericType && decType.GetGenericTypeDefinition() == typeof(List<>))
                    {
                        Type elementType = decType.GetGenericArguments().FirstOrDefault();
                        if (elementType != null)
                        {
                            var _dec_obj = (IList)Activator.CreateInstance(decType);
                            Type _obj_type = _dec_obj.GetType();
                            var method = _obj_type.GetMethod("Add");
                            (obj as List<object>)?.ForEach(s => {
                                var s_type = s.GetType();
                                if (s_type == typeof(JsonElement))
                                    method?.Invoke(_dec_obj, new object[] { JsonElementToObject(s) });
                                else if (s_type == typeof(Dictionary<string, object>) || s_type == typeof(Dictionary<string, object?>))
                                    method?.Invoke(_dec_obj, new object[] { CopyObjectByType(s, elementType) });
                                else
                                    method?.Invoke(_dec_obj, new object[] { Convert.ChangeType(s, elementType) });
                            });
                             return _dec_obj;
                        }
                        else
                           return  dataType.DefaultValue();
                    }
                    else
                    return obj.CopyObjectByType(decType);
                }
                if (decType.IsEnum)
                    return Enum.TryParse(decType, obj == null ? string.Empty : dataType == decType ? obj.ToString() : string.Empty, out object? result) ? result : null;
                if (decType == typeof(string))
                    return obj == null ? string.Empty : obj.ToString();
                if (decType == typeof(Guid))
                    return obj == null ? Guid.Empty : Guid.TryParse(obj.ToString(), out Guid result) ? result : Guid.Empty;
                if (obj != null)
                    return Convert.ChangeType(obj, decType);
                return Activator.CreateInstance(decType);

            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public static bool IsIP(this string _ip)
        {
            if (string.IsNullOrEmpty(_ip)) return false;
            string rex = @"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
            return Regex.IsMatch(_ip, rex);
        }
        public static bool IsPort(this string _port)
        {
            if (string.IsNullOrEmpty(_port)) return false;
            string rex = @"^\d{1,5}$";
            if (!Regex.IsMatch(_port, rex))
                return false;
            return int.TryParse(_port, out int port) && port > 0 && port < 65536;
        }
        public static bool IsPort(this int _port) => _port > 0 && _port < 65536;
        public static bool IsUrl(this string _baseUrl)
        {
            string Url = @"^(http|https)://[a-z0-9A-Z\u4e00-\u9fa5\-\.]+\.[a-zA-Z\u4e00-\u9fa5]{2,}(:[0-9]+)?(/.*)?";
            return Regex.IsMatch(_baseUrl, Url);
        }
        public static bool IsSSL(this string _baseUrl)
        {
            string https = @"^https";
            return Regex.IsMatch(_baseUrl, https);
        }
        public static string GetDomainName(this string _baseUrl)
        {
            Match math = Regex.Match(_baseUrl, @"[a-z0-9A-Z\u4e00-\u9fa5\-\.]+\.[a-zA-Z\u4e00-\u9fa5]{2,}(:[0-9]+)?");
            if (math.Success)
                return math.Value;
            return string.Empty;
        }
        public static bool IsDomain(this string _baseUrl)
            => Regex.IsMatch(_baseUrl, @"[a-z0-9A-Z\u4e00-\u9fa5\-\.]+\.[a-zA-Z\u4e00-\u9fa5]{2,}(:[0-9]+)?");
        public static MqttQualityOfServiceLevel GetQOS(this QOSEnum qOS)
            => qOS switch
            {
                QOSEnum.AtMostOnce => MqttQualityOfServiceLevel.AtMostOnce,
                QOSEnum.AtLeastOnce => MqttQualityOfServiceLevel.AtLeastOnce,
                _ => MqttQualityOfServiceLevel.ExactlyOnce
            };

    }
}
